 
class RedGreenRefactoring::Assertions
  
  class AssertionFail < Exception
    def initialize message, original_exception = nil      
      if original_exception
        super(message + "\n" + original_exception.message)
        @fake_trace = original_exception.backtrace
      else
        super(message)
      end
    end
    
    def backtrace
      @fake_trace || super
    end
  end
  
  def assert value, message = nil
    if value
      pass
    else
      raise AssertionFail.new(message || "Expected true, got #{value.inspect}")
    end
  end
  
  def assert_equal got, expected, message = nil
    assert expected.==(got)
  rescue AssertionFail
    unless message
      e = expected.inspect
      g = got.inspect
      if e.length > 200 && expected.class != got.class
        e = expected.class.to_s
        g = got.class.to_s
      end
      message = "Expected #{e}, got #{g}"
    end
    raise_fail message 
  end
  
  def assert_include haystack, needle, message = nil
    assert haystack.include?(needle), 
      message || "#{haystack.inspect} is expected to include #{needle.inspect}, but it doesn't."
  end
  
  def assert_not_include haystack, needle, message = nil
    assert !haystack.include?(needle), 
      message || "#{haystack.inspect} is expected NOT to include #{needle.inspect}, but it doesn't."
  end
  
  def assert_is_a instance, klass, message = nil
    assert instance.is_a?(klass), 
      message || "#{instance} expected to be an instance of #{klass}, but was #{instance.class} (#{instance})."
  end
  
  def assert_match string, expression, message = nil
    raise 'expression cannot be a string' if expression.is_a? String
    message ||= "String '#{string}' doesn't match expression '#{expression}'."
    assert !expression.match(string).nil?, message
  end
  
  def assert_not_match string, expression, message = nil
    assert expression.match(string).nil?,
      message || "String '#{string}' should not match given expression '#{expression}'."
  end
  
  # TODO - check if mock has been called at least once!
  def assert_called object, method, message = nil, stop = nil, &block
    calls = 0
    if stop == nil
      stop = !object.respond_to?(method.to_s)
    end
    mock = Magic::Mock.method(object, method) do |*args|
      calls += 1
      unless stop
        return super(*args)
      end
    end
    unless block_given?
      raise 'No blovk given!'
    end
    yield(mock)
    if calls == 0
      message ||= "Method #{method} was not called during block but was expected to."
    elsif calls > 1
      message ||= "Method #{method} was called #{calls} times during block but was expected to get only one call."
    end
    assert_equal calls, 1, message
  end
      
  def assert_called_stop object, method, message = nil, &block
    assert_called object, method, message, true, &block
  end
  
  #refactoring here
  def assert_called_with_params object, method, expected_args, stop = false, &block
    calls = 0
    recorded_arguments = nil
    mock = Magic::Mock.method(object, method) do |*args|
      calls += 1
      recorded_arguments = args
      unless stop
        super(*args)
      end
    end
    unless block_given?
      raise 'No blovk given!'
    end
    yield(mock)
    if calls == 0
      raise_fail "Method #{method} was not called during block but was expected to."
    elsif calls > 1
      raise_fail "Method #{method} was called #{calls} times during block but was expected to get only one call."
    end
    assert_equal recorded_arguments, expected_args,
        "The arguments passed to the method #{recorded_arguments.inspect} differ from expected: #{expected_args.inspect}."
    pass
  end
  
  def assert_called_with_params_stop object, method, expected_args, &block
    assert_called_with_params object, method, expected_args, true, &block
  end

  
  def assert_not_called object, method, message = nil
    calls = 0
    mock = Magic::Mock.method(object, method) do
      calls += 1
      super
    end
    yield(mock)
    message ||= "Method #{method} was called #{calls} times during the block but we expected no calls."
    assert_equal calls, 0, message
  end
  
  # We hope for no exception.
  # If one occurs, we wrap it into AssertionFail and use the old trace.
  def assert_no_exception
    begin
      result = yield
      pass
      result
    rescue Exception => e
      raise AssertionFail.new("No exception should be thrown, but was.", e)
    end
  end
  
  def raise_fail message
    raise AssertionFail.new(message)
  end
  
  def assert_exception(type = nil)
    begin
      yield
      fail = true
    rescue Exception => e      
      if type.nil?
        pass
      else
        assert_is_a e, type
      end
    ensure
      if fail
        raise AssertionFail.new("Exception expected, but none was thrown.")
      end
    end
  end
end


